﻿namespace WLFramework.Web
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Reflection;
    using System.Text;
    using System.Web;

    public static class AjaxRoute
    {
        private delegate void Execute(HttpContext context);
        private delegate void ErrorExecute(HttpContext context, Exception ex);

        private class RouteContext
        {
            public Dictionary<string, Execute> Methods { get; set; }

            public Execute OnBefore { get; set; }

            public Execute OnAfter { get; set; }

            public ErrorExecute OnError { get; set; }

            public RouteContext(
                Execute onBefore = null,
                Execute onAfter = null,
                ErrorExecute onError = null)
            {
                this.Methods = new Dictionary<string, Execute>();
                this.OnBefore = onBefore;
                this.OnAfter = onAfter;
                this.OnError = onError;
            }
        }

        private static string area = null;
        public static string Area
        {
            get { return AjaxRoute.area; }
            set { AjaxRoute.area = value; }
        }

        /// <summary>
        /// 当area为空时，忽略这些地址
        /// </summary>
        private static Dictionary<string, bool> ignoreScopes =
            new Dictionary<string, bool>(StringComparer.OrdinalIgnoreCase) 
        {
            {"img", true},
            {"imgs", true},
            {"image", true},
            {"images", true},
            {"javascript", true},
            {"js", true},
            {"content", true},
            {"contents", true},
            {"css", true},
        };

        private static Dictionary<string, RouteContext> _caches =
            new Dictionary<string, RouteContext>();

        public static void Register(Type type)
        {
            var scope = type.GetCustomAttributes(typeof(AjaxScopeAttribute), false).Cast<AjaxScopeAttribute>().FirstOrDefault();
            if (scope == null) return;

            var methods = type.GetMethods(BindingFlags.Public |
                                          BindingFlags.Static |
                                          BindingFlags.InvokeMethod);
            var actions = new List<MethodInfo>();
            Execute before = null;
            Execute after = null;
            ErrorExecute error = null;
            foreach (var method in methods)
            {
                var attr = method.GetCustomAttributes(typeof(AjaxMethodAttribute), false).Cast<AjaxMethodAttribute>().FirstOrDefault();
                if (attr == null)
                    continue;
                if (attr is AjaxBeforeMethodAttribute)
                {
                    before = (Execute)Delegate.CreateDelegate(typeof(Execute), method);
                }
                else if (attr is AjaxAfterMethodAttribute)
                {
                    after = (Execute)Delegate.CreateDelegate(typeof(Execute), method);
                }
                else if (attr is AjaxErrorAttribute)
                {
                    error = (ErrorExecute)Delegate.CreateDelegate(typeof(ErrorExecute), method);
                }
                else if (attr is AjaxActionAttribute)
                {
                    actions.Add(method);
                }
            }

            var route = new RouteContext(before, after, error);
            foreach (var m in actions)
            {
                var attr = m.GetCustomAttributes(typeof(AjaxActionAttribute), false).Cast<AjaxActionAttribute>().FirstOrDefault();

                route.Methods.Add(
                    attr.Name,
                    (Execute)Delegate.CreateDelegate(typeof(Execute), m));
            }

            _caches.Add(scope.Name, route);
        }

        public static void Route(HttpContext context)
        {
            var split = context.Request.Path.Split('/');
            if (split.Length < 3 || split.Length > 4)
                return;

            RouteContext route;
            Execute exec;
            string scope;
            string action;

            if (area == null)
            {
                if (split.Length != 3 ||
                    ignoreScopes.ContainsKey(split[1]))
                    return;

                scope = split[1];
                action = split[2];
            }
            else
            {
                if (split.Length != 4 ||
                    string.CompareOrdinal(split[1], area) != 0)
                    return;

                scope = split[2];
                action = split[3];
            }

            if (!_caches.TryGetValue(scope, out route) ||
                !route.Methods.TryGetValue(action, out exec))
            {
                return;
            }

            try
            {
                if (route.OnBefore != null)
                    route.OnBefore(context);

                exec(context);

                if (route.OnAfter != null)
                    route.OnAfter(context);

                context.Response.End();
            }
            catch (Exception ex)
            {
                if (route.OnError != null)
                    route.OnError(context, ex);
                else
                    throw;
            }
        }
    }
}
